package com.flow.engine.service.impl;

import com.alibaba.fastjson2.JSON;
import com.alibaba.fastjson2.TypeReference;
import com.baomidou.mybatisplus.extension.service.impl.ServiceImpl;
import com.flow.engine.entity.*;
import com.flow.engine.enums.standard.ApprovalState;
import com.flow.engine.enums.standard.FlowAuditType;
import com.flow.engine.exception.NodeApprovalStateNotWaitingException;
import com.flow.engine.handle.FlowObtainAuditUserInfo;
import com.flow.engine.mapper.FlowMapper;
import com.flow.engine.service.*;
import com.flow.engine.service.handler.AuditEndHandler;
import com.flow.engine.service.handler.AuditFailHandler;
import com.flow.engine.service.handler.AuditPassHandler;
import com.flow.engine.service.handler.AuditSmsHandler;
import com.flow.engine.service.obtainAuditUser.FlowObtainAuditUserInfoImpl;
import org.springframework.beans.factory.annotation.Autowired;
import org.springframework.stereotype.Service;
import org.springframework.transaction.annotation.Transactional;
import org.springframework.util.CollectionUtils;

import java.time.LocalDateTime;
import java.util.*;

/**
 * @author ly163
 */
@Service
@Transactional
public class FlowServiceImpl extends ServiceImpl<FlowMapper, Flow> implements FlowService {

    @Autowired
    private FlowTemplateService flowTemplateService;

    @Autowired
    private FlowNodeTemplateService flowNodeTemplateService;

    @Autowired
    private FlowNodeService flowNodeService;

    @Autowired
    private FlowFatherChildNodeService flowFatherChildNodeService;

    @Autowired
    private FlowEngineService flowEngineService;

    @Autowired
    private FlowAuditUserService flowAuditUserService;

    @Autowired
    private FlowObtainAuditUserInfo flowObtainAuditUserInfo;

    @Autowired
    private FlowMapper mapper;

    @Autowired
    private AuditFailHandler failHandler;

    @Autowired
    private AuditEndHandler endHandler;

    @Autowired
    private AuditPassHandler passHandler;

    @Autowired
    private AuditSmsHandler smsHandler;

    @Override
    @Transactional(rollbackFor = Exception.class)
    public Boolean createFlow(Long flowTemplateId, List<FlowNodeTemplate> flowNodeTemplatesParam) {
        User u = getCurrentLoginUser();
        FlowTemplate flowTemplate = flowTemplateService.getById(flowTemplateId);
        Flow flow = Flow.builder().status(0).icon(flowTemplate.getIcon()).titleName(flowTemplate.getTitleName()).build();
        flow.setCreateId("currentId");
        flow.setCreateName("currentName");
        flow.setCreateTime(LocalDateTime.now());
        mapper.insert(flow);

        Long flowId = flow.getId();
        List<FlowNodeTemplate> flowNodeTemplates = flowNodeTemplateService.queryFlowById(flowTemplateId);
        Map<Long, FlowNode> nodeMap = bulidFlowNodeMap(flowNodeTemplates, flowId);
        flowNodeService.saveBatch(nodeMap.values());
        List<FlowAuditUser> flowAuditUsers = buildAuditUser(nodeMap, flowNodeTemplates, flowNodeTemplatesParam, u);
        flowAuditUserService.saveBatch(flowAuditUsers);
        List<FlowFatherChildNode> flowFatherChildNodes = builderFatherChildRef(flowNodeTemplates, nodeMap);
        return flowFatherChildNodeService.saveBatch(flowFatherChildNodes);
    }

    private List<FlowAuditUser> buildAuditUser(Map<Long, FlowNode> nodeMap, List<FlowNodeTemplate> flowNodeTemplates, List<FlowNodeTemplate> flowNodeTemplatesParam, User user) {
        Map<Long, FlowNodeTemplate> flowNodeTemplateMapParam = new HashMap<>(flowNodeTemplatesParam.size());
        Map<Long, FlowNodeTemplate> flowNodeTemplateMap = new HashMap<>(flowNodeTemplates.size());
        flowNodeTemplatesParam.forEach(template -> flowNodeTemplateMapParam.put(template.getId(), template));
        flowNodeTemplates.forEach(template -> flowNodeTemplateMap.put(template.getId(), template));

        List<FlowAuditUser> auditUsers = new ArrayList<>();
        for (FlowNodeTemplate flowNodeTemplate : flowNodeTemplates) {
            FlowAuditType flowAuditType = flowNodeTemplate.getFlowAuditType();
            List<User> res = null;
            switch (flowAuditType) {
                case DM -> res = processDM(flowNodeTemplate, flowNodeTemplateMap, user);
                case C_DM -> res = flowObtainAuditUserInfo.getCurrentDeptAuditUser(flowNodeTemplate.getFlowAuditValue());
                case CHOSE -> res = JSON.parseObject(flowNodeTemplate.getFlowAuditValue(), new TypeReference<>() {
                });
                case SELF ->
                        res = JSON.parseObject(flowNodeTemplateMapParam.get(flowNodeTemplate.getId()).getFlowAuditValue(), new TypeReference<>() {
                        });
                case ROLE ->
                        res = flowObtainAuditUserInfo.getCurrentUserRecentlyRoleAuditUser(user.getId(), flowNodeTemplate.getFlowAuditValue());
            }
            if (!CollectionUtils.isEmpty(res)) {
                res.forEach(u -> auditUsers.add(FlowAuditUser.builder().flowNodeId(nodeMap.get(flowNodeTemplate.getId()).getId()).auditUserId(u.getId()).auditUserName(u.getName()).approvalState(ApprovalState.WAITING).build()));
            }
        }
        return auditUsers;
    }

    private User getCurrentLoginUser() {
        return new User("1", "glt");
    }

    @Override
    public Boolean audit(Long nodeId, String remark, Boolean auditStatus, User user) {


        FlowNode flowNode = flowNodeService.selectById(nodeId);

        if (ApprovalState.WAITING != flowNode.getApprovalState()) {
            throw new NodeApprovalStateNotWaitingException();
        }
        ApprovalState nodeApprovalState = auditStatus ? ApprovalState.PASS : ApprovalState.REJECT;
        flowNode.setApprovalState(nodeApprovalState);
        flowNode.setRemark(remark);
        flowNode.setUpdateId(user.getId().toString());
        flowNode.setUpdateTime(LocalDateTime.now());
        flowNode.setUpdateName(user.getName());
        Boolean b = Boolean.FALSE;
        switch (nodeApprovalState) {
            case PASS -> b = flowEngineService.auditPass(flowNode, user, endHandler, passHandler, smsHandler);
            case REJECT -> b = flowEngineService.auditReject(flowNode, user, failHandler);
        }
        return b;
    }

    private List<FlowFatherChildNode> builderFatherChildRef(List<FlowNodeTemplate> flowNodeTemplates, Map<Long, FlowNode> nodeMap) {
        List<FlowFatherChildNode> flowFatherChildNodes = new ArrayList<>();
        flowNodeTemplates.forEach(template -> {
            Long templateNodeId = template.getId();

            FlowNode currentNode = nodeMap.get(templateNodeId);

            List<Long> parentIds = template.getParentIds();
            List<FlowNode> parentNodes = getFlowNodeByIds(parentIds, nodeMap);

            List<Long> nextIds = template.getNextIds();
            List<FlowNode> nextNodes = getFlowNodeByIds(nextIds, nodeMap);

            FlowFatherChildNode flowNode = FlowFatherChildNode.builder().currentId(currentNode.getId()).flowId(currentNode.getFlowId()).type("flowNode").build();
            List<FlowFatherChildNode> tempFlowFatherChildNodes = new ArrayList<>();

            if (!CollectionUtils.isEmpty(parentNodes)) {
                for (FlowNode parentNode : parentNodes) {
                    tempFlowFatherChildNodes.add(flowNode.clone().setParentId(parentNode.getId()));
                }
            } else {
                tempFlowFatherChildNodes.add(flowNode);
            }
            for (FlowFatherChildNode tempNode : tempFlowFatherChildNodes) {
                if (!CollectionUtils.isEmpty(nextNodes)) {
                    for (FlowNode nextNode : nextNodes) {
                        flowFatherChildNodes.add(tempNode.clone().setNextId(nextNode.getId()));
                    }
                } else {
                    flowFatherChildNodes.add(tempNode);
                }
            }
        });
        return flowFatherChildNodes;
    }

    private Map<Long, FlowNode> bulidFlowNodeMap(List<FlowNodeTemplate> flowNodeTemplates, Long flowId) {
        Map<Long, FlowNode> nodeMap = new HashMap<>();
        flowNodeTemplates.forEach(template ->
                nodeMap.put(template.getId(), new FlowNode()
                        .setFlowId(flowId)
                        .setNextCondition(template.getNextCondition())
                        .setFlowNodeName(template.getFlowNodeName())
                        .setFlowAuditType(template.getFlowAuditType())
                        .setFlowAuditValue(template.getFlowAuditValue())
                        .setCurrentCondition(template.getNextCondition())
                        .setParentIds(template.getParentIds())
                        .setNextIds(template.getNextIds())
                        .setApprovalState(ApprovalState.WAITING)));
        return nodeMap;
    }


    private List<User> processDM(FlowNodeTemplate flowNode, Map<Long, FlowNodeTemplate> nodeMap, User user) {
        List<Long> parentIds = flowNode.getParentIds();
        return CollectionUtils.isEmpty(parentIds)
                ?
                flowObtainAuditUserInfo.getCurrentUserDeptAuditUser(user.getId())
                :
                flowObtainAuditUserInfo.getCurrentDeptParentDeptAuditUser(nodeMap.get(flowNode.getId()).getFlowAuditValue());
    }

    private List<FlowNode> getFlowNodeByIds(List<Long> ids, Map<Long, FlowNode> nodeMap) {
        List<FlowNode> flowNodes = new ArrayList<>();
        if (!CollectionUtils.isEmpty(ids)) {
            ids.forEach(id -> flowNodes.add(nodeMap.get(id)));
        }
        return flowNodes;
    }

//    private Map<String, String> getCurrentUserDeptAuditUser(String userId) {
//        // 根据用户ID查询 当前申请者所在部门有审核权限的人
//        return new HashMap<>();
//    }
//
//    private Map<String, String> getCurrentDeptParentDeptAuditUser(String deptId) {
//        // 根据当前部门ID 查询当前部门的上级部门，然后查询上级部门有审核权限的人
//        // 查询当前部门的上级部门
//        String parentDeptId = "deptId";
//        return getCurrentDeptAuditUser(parentDeptId);
//    }
//
//    private Map<String, String> getCurrentDeptAuditUser(String deptId) {
//        return new HashMap<>();
//    }
//
//    private Map<String, String> getCurrentUserRecentlyRoleAuditUser(String userId, String roleId) {
//        //
//        return new HashMap<>();
//    }

}
